#pragma once

#include<iostream>
#include<string>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<strings.h>
#include<functional>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<pthread.h>

#include"log.hpp"
#include"ThreadPool.hpp"
#include"daemon.hpp"
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISREN_ERR,
    OPEN_ERR,
};

static const uint16_t gport = 10086; //默认端口号，外部没传就调用这个
static const int gbacklog = 5;

// class tcpServer;
// class ThreadData //这里在实现多线程版本的时候定义的，和进程池中的ThreadData重定义了
// {
// public:
//     ThreadData(tcpServer* ts, int& sockfd)
//     :_self(ts)
//     ,_sockfd(sockfd)
//     {}

//     ~ThreadData(){}
// public: //这里是懒得写get/set接口，直接public
//     tcpServer* _self;
//     int _sockfd;
// };


class tcpServer
{
public:
    tcpServer(const uint16_t& port = gport)
    :_listensockfd(-1)
    ,_port(port)
    {}

    void InitServer()
    {
        //1.创建tcp套接字(socket)
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); //SOCK_STREAM表示要创建TCP套接字
        if(_listensockfd < 0)
        {
            logMessage(FATAL, "create socket error");
            exit(SOCKET_ERR);
        }
        logMessage(NORMAL, "create socket success: %d", _listensockfd);

        //2.bind 绑定自己的网络信息【ip和port等】
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY; // 任意地址bind
        if(bind(_listensockfd, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL, "bind success");

        // 由于tcp是需要建立连接的，因此这里需要设置一个监听
        //3. 设置socket为监听状态【需要知道客户端是否给我发消息】
        if(listen(_listensockfd, gbacklog) < 0)
        {
            logMessage(FATAL, "listen socket error");
            exit(LISREN_ERR);
        }
        logMessage(NORMAL, "listen socket success");


    }

    void Start()
    {
        ThreadPool<Task>::getInstance()->run(); //单例模式，线程池初始化
        logMessage(NORMAL, "ThreadPool create success");
        for(;;)
        {
            // tcp不能直接接受或发送消息，需要先建立链接
            // 1.server获取新链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr*)&peer, &len); 
            if(sockfd < 0)
            {
                logMessage(ERROR, "accept error, next"); //获取新链接失败不会导致程序崩溃，再获取就行了
                continue;
            }
            logMessage(NORMAL, "accept a new link success! new sock: %d", sockfd);

            //2.用获取新链接的套接字来进行通信【tcp通信面向字节流, 后序都是文件操作】
            // serverIO(sockfd); 
            // close(sockfd); 
            //如果不在通信，就要关闭套接字.【这是因为文件描述符本质就是数组下标， 能创建的文件描述符有上限的， 不及时关闭就会导致文件描述符泄漏】

            // 由于tcp的通信逻辑在读取数据的时候就是一个死循环，因此当有多个客户端进来的时候，就无法为多个客户端提供服务
            // 为了解决这个问题，就需要设计出多个解决方案【多进程，多线程， 线程池，多路转接】
            
            //2.1多进程版本1【多进程是一个可行的方案，但是实际上不会用，因为创建进程的代价相比线程来说还是太大了】
            /*
            pid_t id = fork();
            if(id == 0)
            {
                //子进程不需要监听， 子进程只需要提供服务
                close(_listensockfd); // 虽然不关闭它也不会影响程序的运行，但是防止对其进行误操作
                if(fork() > 0) //一旦这样做了，子进程就会原地退出，父进程就可以顺利执行waitpid
                    exit(0);
                // 孙子进程来执行下面的任务， 此时孙子进程变成孤儿进程了，由OS领养
                serverIO(sockfd);
                close(sockfd);
                exit(1);
            }
            // 父进程就等待子进程
            // 这里是阻塞式等待，若子进程内部不创建孙子进程，这里这样写是不行的。因为阻塞式等待，一旦子进程不出来，就会卡在这里，无法再次accept，不能为其他客户端提供服务
            // 这里我会用一个小技巧一样的代码，解决这里的问题，我非要用阻塞式等待，其实只要在子进程中创建一个孙子进程去执行任务即可
            int n = waitpid(id, nullptr, 0); 
            if(n > 0)
            {
                cout << "waitpid success: " << n << endl;
            }
            close(sockfd); // 这个文件描述符必须关掉，因为不关的话会导致文件描述符泄漏
            */
            //2.2多进程版本2
            /*
            //弄成非阻塞等待，但是要结合信号捕捉，当子进程退出的时候，捕捉其信号再让父进程去捕捉它、【signal(SIGCHLD, SIG_IGN)】
            signal(SIGCHLD, SIG_IGN);//SIG_IGN是LinuxOS提供的一个专门用来处理子进程资源释放的函数
            pid_t id = fork();
            if(id == 0)
            {
                close(_listensockfd); 
                serverIO(sockfd);
                close(sockfd);
                exit(1);
            }
            // 不需要父进程等待子进程了
            close(sockfd); // 文件描述符必须关掉，因为不关的话会导致文件描述符泄漏
            */

           /*
            //2.3多线程版本
            pthread_t tid;
            ThreadData* td = new ThreadData(this, sockfd); //通过该类型对象，将服务器对象和文件描述符(套接字)传过去

            pthread_create(&tid, nullptr, threadRoutine, td);

            // pthread_join(tid, nullptr); //这里不能线程等待，因为也是阻塞式等待，线程的解决方案很简单，线程分离让子线程自己去执行就可以了
            */

            //不管是多进程还是多线程都可以解决我们的问题，但是不管是多线程还是多进程，都是等到客户端到来之后才创建线程和进程。存在频繁创建的问题
            // 还有一个问题是，能服务多少客户的问题，有几百个客户端，就要创建几百个进程/线程。这样线程切换的频率也会很多，开销大
            // 一旦客户端数量更多了，就无法服务了，因为线程数量有上限，几千已经很多了，这样就无法应对高并发的场景

            //因此这里为了应付高并发的场景，就需要进程池/线程池
            // 2.4 线程池版本
            ThreadPool<Task>::getInstance()->Push(Task(sockfd, serverIO));

        }
    }

    // static void* threadRoutine(void* args) //必须要static，因为类内函数的第一个形参都是this指针
    // {
    //     pthread_detach(pthread_self());
    //     ThreadData* td = static_cast<ThreadData*>(args);
    //     td->_self->serverIO(td->_sockfd);
    //     delete td; //td的使命结束了，释放
    //     close(td->_sockfd); //用完了就要释放, 这里是子线程，关闭的就是该进程的套接字
    //     return nullptr;
    // }

    ~tcpServer(){}

private:
    int _listensockfd; //listen套接字【不进行数据通信，只是用来监听是否有链接到来，建立新链接】
    uint16_t _port;
    string _ip;

};